﻿using System;

namespace Game500.ClientServerCommon.Src.Networking
{
  /// <summary>
  /// Represents network packet.
  /// Structure:
  /// [Size with header in bytes:4b][Type of packet:1b][Inner Data:(size - 4 - 1) b]
  /// </summary>
  public class NetworkPacket
  {
    private const byte SizeFieldLen = 4;
    private const byte TypeFieldLen = 1;

    private readonly byte[] myInnerData;
    private readonly NetworkPacketType myType;

    private NetworkPacket(NetworkPacketType type, byte[] innerData)
    {
      myType = type;
      myInnerData = innerData;
    }

    public NetworkPacketType Type
    {
      get { return myType; }
    }

    public byte[] InnerData
    {
      get { return myInnerData; }
    }

    public static NetworkPacket CreatePacket(NetworkPacketType type, byte[] source, int length)
    {
      var data = new byte[length];
      Array.Copy(source, 0, data, 0, length);
      var packet = new NetworkPacket(type, data);
      return packet;
    }

    public static NetworkPacket CreatePacket(NetworkPacketType type)
    {
      var packet = new NetworkPacket(type, null);
      return packet;
    }

    /// <summary>
    /// Pack lenght bytes from source adding header with length and specified type
    /// </summary>
    /// <param name="type">Type of required packet</param>
    /// <param name="source">Data to pack</param>
    /// <param name="length">Count of bytes to pack</param>
    /// <param name="dest">Destination array, must have length at least (source.Length + 5)</param>		
    /// <returns>Size of packed data</returns>
    public static int PackData(NetworkPacketType type, byte[] source, int length, byte[] dest)
    {
      int packetLen = SizeFieldLen + TypeFieldLen + length;
      int offset = 0;
      //Write size
      Array.Copy(BitConverter.GetBytes(packetLen), 0, dest, offset, SizeFieldLen);
      offset += SizeFieldLen;
      //Write type
      Array.Copy(BitConverter.GetBytes((byte)type), 0, dest, offset, TypeFieldLen);
      offset += TypeFieldLen;
      //Write data
      Array.Copy(source, 0, dest, offset, length);
      return packetLen;
    }

    public byte[] GetBytes()
    {
      int length = sizeof(byte);
      if (myInnerData != null)
      {
        length += myInnerData.Length;
      }
      var result = new byte[length];
      result[0] = (byte)myType;
      if (myInnerData != null)
      {
        Array.Copy(myInnerData, 0, result, 1, myInnerData.Length);
      }
      return result;
    }

    /// <summary>
    /// Parse packet from raw data using count of bytes specified in rawData header
    /// </summary>
    /// <param name="rawData">source raw data having header with size and type</param>
    /// <returns>New NetworkPacket instance</returns>
    public static NetworkPacket ParsePacket(byte[] rawData, int length)
    {
      var type = (NetworkPacketType)rawData[0];
      int dataLen = length - sizeof(byte);
      var innerData = new byte[dataLen];
      Array.Copy(rawData, TypeFieldLen, innerData, 0, dataLen);
      var packet = new NetworkPacket(type, innerData);
      return packet;
    }

    public static bool TryParsePacket(byte[] data, int length, out NetworkPacket packet)
    {
      packet = null;
      if (data.Length < TypeFieldLen || length < TypeFieldLen)
      {
        return false;
      }
      packet = ParsePacket(data, length);
      return true;
    }
  }

  public enum NetworkPacketType : byte
  {
    SceneFramePacket,
    GamerActionPacket,
    GameInfoPacket,
    GamerInfoPacket,
    ShotEventPacket,
    ExplosionEventPacket,
    PlayerLimitReachedPacket
  }
}